DLO-JZ Optimisation de l'apprentissage - Jour 2¶

Optimisation système d'une boucle d'apprentissage Resnet-50.

car

Objet du notebook¶

Le but de ce notebook est d'optimiser un code d'apprentissage d'un modèle Resnet-50 sur Imagenet pour Jean Zay en implémentant :

  • TP 1 : la distribution (Data Parallelism)
  • TP 2 : le Profiler PyTorch
  • TP 3 : l'optimisation du Dataloader

Les cellules dans ce notebook ne sont pas prévues pour être modifiées, sauf rares exceptions indiquées dans les commentaires. Les TP se feront en modifiant les codes dlojz2_X.py.

Les directives de modification seront marquées par l'étiquette TODO dans le notebook suivant.

Les solutions sont présentes dans le répertoire solutions/.

Notebook rédigé par l'équipe assistance IA de l'IDRIS, janvier 2024


Environnement de calcul¶

Un module PyTorch doit avoir été chargé pour le bon fonctionnement de ce Notebook. Nécessairement, le module pytorch-gpu/py3/2.1.1 :

In [1]:
!module list
Currently Loaded Modulefiles:
 1) cuda/11.8.0              5) openmpi/4.1.5-cuda   9) sparsehash/2.0.3       
 2) nccl/2.18.5-1-cuda       6) intel-mkl/2020.4    10) libjpeg-turbo/2.1.3    
 3) cudnn/8.7.0.84-cuda      7) magma/2.7.1-cuda    11) pytorch-gpu/py3/2.1.1  
 4) gcc/8.5.0(8.3.1:8.4.1)   8) sox/14.4.2          
>

Les fonctions python de gestion de queue SLURM développées par l'IDRIS et les fonctions dédiées à la formation DLO-JZ sont à importer.

Le module d'environnement pour les jobs et la taille des images sont fixés pour ce notebook.

TODO : choisir un pseudonyme (maximum 5 caractères) pour vous différencier dans la queue SLURM pendant la formation.

In [3]:
from idr_pytools import display_slurm_queue, gpu_jobs_submitter, search_log
from dlojz_tools import controle_technique, compare, GPU_underthehood, plot_accuracy, lrfind_plot, imagenet_starter, comm_profiler, turbo_profiler, BatchNorm_view
MODULE = 'pytorch-gpu/py3/2.1.1'
image_size = 224
account = 'for@a100'
name = 'pseudo'   ## Pseudonyme à choisir

assert name != 'pseudo' and name != '', 'please choose a pseudo'

Création d'un répertoire checkpoints/ si cela n'a pas déjà été fait.

In [4]:
!mkdir -p checkpoints

Gestion de la queue SLURM¶

Pour afficher vos jobs dans la queue SLURM :

In [ ]:
display_slurm_queue(name)

Remarque: cette fonction sera utilisée plusieurs fois dans ce notebook. Elle permet d'afficher la queue de manière dynamique, rafraichie toutes les 5 secondes. Elle ne s'arrête que lorsque la queue est vide. Si vous désirez reprendre la main sur le notebook, il vous suffira d'arrêter manuellement la cellule avec le bouton stop. Cela n'a bien sûr aucun impact sur les jobs soumis.

Si vous voulez retirer TOUS vos jobs de la queue SLURM, décommenter et exécuter la cellule suivante :

In [ ]:
#!scancel -u $USER

Si vous voulez retirer UN de vos jobs de la queue SLURM, décommenter, compléter et exécuter la cellule suivante :

In [ ]:
#!scancel <jobid>

Debug¶

Cette partie debug permet d'afficher les fichiers de sortie et les fichiers d'erreur du job.

Il est nécessaire dans la cellule suivante (en décommentant) d'indiquer le jobid correspondant sous le format suivant.

Remarque : dans ce notebook, lorsque vous soumettrez un job, vous recevrez en retour le numéro du job dans le format suivant : jobid = ['123456']. La cellule ci-dessous peut ainsi être facilement actualisée."

In [ ]:
jobid = ['1493206']

Fichier de sortie :

In [ ]:
%cat {search_log(contains=jobid[0])[0]}

Fichier d'erreur :

In [ ]:
%cat {search_log(contains=jobid[0], with_err=True)['stderr'][0]}

Différence entre deux scripts¶

Pour comparer son code avec les solutions mises à disposition, la fonction suivante permet d'afficher une page HTML contenant un différentiel de fichiers texte.

In [ ]:
s1 = "./dlojz2_1.py"
s2 = "./solutions/dlojz2_1.py"
compare(s1, s2)

Voir le résultat du différentiel de fichiers sur la page suivante (attention au spoil !) :

compare.html


Garage - Mise à niveau¶

On fixe la taille d'image pour ce TP.

In [5]:
image_size = 224

On choisit le batch size optimal d'après les expériences du Jour 1.

In [6]:
## Choisir un batch size optimal
bs_optim = 512   ##TODO

TP2_1 : Distribution - Parallélisme de données¶

Voir la documentation de l'IDRIS.

TODO : dans le script dlojz2_1.py :

  • Importer les librairies liées à la distribution et au Data Parallelism.

  • Configurer et initialiser l'environnement parallèle.

  • Associer le bon GPU alloué au process actif.

  • Basculer le modèle en mode DistributedDataParallelism pour qu'il soit dupliqué sur les différents GPU.

  • Définir les samplers distribués train_sampler et val_sampler et les utiliser dans train_loader et val_loader respectivement. Attention, le shuffling devra être délégué aux samplers.

  • Au tout début de la boucle d'apprentissage, indiquer au sampler l'epoch en cours afin d'obtenir un shuffling différent à chaque epoch.

Soumission du job. Attention vous sollicitez les noeuds de calcul à ce moment-là.

Pour soumettre le job, veuillez basculer la cellule suivante du mode Raw NBConvert au mode Code.

In [7]:
command = f'./dlojz2_1.py -b {bs_optim} --image-size {image_size} --test --chkpt' 
n_gpu = 4
jobid = gpu_jobs_submitter(command, n_gpu, MODULE, name=name,
                    account=account, time_max='00:10:00')
print(f'jobid = {jobid}')
batch job 0: 4 GPUs distributed on 1 nodes with 4 tasks / 4 gpus per node and 8 cpus per task
Submitted batch job 890893
jobid = ['890893']

Copier-coller la sortie jobid = ['xxxxx'] dans la cellule suivante.

Puis, rebasculer la cellule précédente en mode Raw NBConvert, afin d'eviter de relancer un job par erreur.

In [8]:
jobid = ['890893']
In [9]:
display_slurm_queue(name)
             JOBID PARTITION     NAME     USER ST       TIME  NODES NODELIST(REASON)
            890893    gpu_p5     toto  ssos938 CG       2:47      1 jean-zay-iam16

 Done!
In [10]:
controle_technique(jobid)
Train throughput: 6157.57 images/second
GPU throughput: 6988.45 images/second
epoch time: 208.21 seconds
-----------
training step time average (fwd/bkwd on GPU): 0.293055 sec (8.2%/109.6%) +/- 0.192701
loading step time average (IO + CPU to GPU transfer): 0.039544 sec +/- 0.096367

Communications¶

Découverte de comm_profiler¶

Pour ce TP, nous avons implémenté un profiler maison léger comm_profiler basé sur les traces de DEBUG de NCCL pour visualiser la quantité et le type de communications collectives échangées pendant une boucle d'apprentissage distribuée sur plusieurs GPU.

À noter : dans le script python dlojz2_1.py les variables de trace de DEBUG NCCL sont configurées comme suit :

if __name__ == '__main__':
    
    os.environ["NCCL_DEBUG"] = "INFO"
    os.environ["NCCL_DEBUG_SUBSYS"] = "INIT,COLL"
    # display info
    ...
In [11]:
comm_profiler(jobid)

Commentaires

BatchNorm Layer & SyncBatchNorm Layer¶

Rappel :

Pendant l'apprentissage, la couche normalise ses sorties en utilisant la moyenne et l'écart type du batch d'entrée. Plus exactement, la couche retourne (batch - mean(batch)) / (var(batch) + epsilon) * weight + bias , avec :

  • epsilon, une petite constante pour éviter la division par 0,
  • weight, un facteur appris (entraîné) avec un calcul de gradient lors de la backpropagation et qui est initialisé à 1,
  • bias, un facteur appris (entraîné) avec un calcul de gradient lors de la backpropagation et qui est initialisé à 0.

Pendant l'inférence ou la validation, la couche normalise ses sorties en utilisant en plus des weight et bias entraînés, les facteurs running_mean et running_var : (batch - running_mean) / (running_var + epsilon) * weight + bias.

running_mean et running_var sont des facteurs non entraînés, mais qui sont mis à jour à chaque itération de batch lors de l'apprentissage, selon la méthode suivante :

  • running_mean = running_mean * momentum + mean(batch) * (1 - momentum)
  • running_var = running_var * momentum + var(batch) * (1 - momentum)
In [12]:
import torchvision.models as models
model = models.resnet50()
In [13]:
BatchNorm_view(jobid, model)

Optionnel : SyncBatchNorm layer¶

Voir la documentation PyTorch.

TODO : dans le script dlojz2_1.py :

  • Juste avant la bascule du modèle en mode DistributedDataParallelism, transformer les couches BatchNorm du modèle en couches SyncBatchNorm.

Soumission du job. Attention vous sollicitez les noeuds de calcul à ce moment-là.

Pour soumettre le job, veuillez basculer la cellule suivante du mode Raw NBConvert au mode Code.

In [20]:
command = f'./dlojz2_1.py -b {bs_optim} --image-size {image_size} --test --chkpt'
n_gpu = 4
jobid_sync = gpu_jobs_submitter(command, n_gpu, MODULE, name=name,
                    account=account, time_max='00:10:00')
print(f'jobid_sync = {jobid_sync}')
batch job 0: 4 GPUs distributed on 1 nodes with 4 tasks / 4 gpus per node and 8 cpus per task
Submitted batch job 890935
jobid_sync = ['890935']

Copier-coller la sortie jobid = ['xxxxx'] dans la cellule suivante.

Puis, rebasculer la cellule précédente en mode Raw NBConvert, afin d'eviter de relancer un job par erreur.

In [21]:
jobid_sync = ['890935']
In [22]:
display_slurm_queue(name)
             JOBID PARTITION     NAME     USER ST       TIME  NODES NODELIST(REASON)
            890935    gpu_p5     toto  ssos938 CG       2:57      1 jean-zay-iam16

 Done!
In [23]:
controle_technique(jobid_sync)
Train throughput: 6004.38 images/second
GPU throughput: 6026.37 images/second
epoch time: 213.52 seconds
-----------
training step time average (fwd/bkwd on GPU): 0.339840 sec (130.9%/43.7%) +/- 1.926840
loading step time average (IO + CPU to GPU transfer): 0.001244 sec +/- 0.004479
In [24]:
BatchNorm_view(jobid + jobid_sync, model, labels=['BN Layer', 'SyncBN Layers'])

Communications¶

In [25]:
comm_profiler(jobid_sync)

Garage


Garage - Mise à niveau¶

On fixe le batch size et la taille d'image pour ce TP.

In [26]:
bs_optim = 512
image_size = 224

TP2_2 : Profiler¶

Implémentation du profiler PyTorch¶

Voir la documentation de l'IDRIS.

TODO : dans le script dlojz2_2.py :

  • Importer les librairies liées au profiler PyTorch.

  • Configurer le profiler et ses paramètres.

# pytorch profiler setup
	prof =  profile(activities=[ProfilerActivity.CPU, ProfilerActivity.CUDA],
                    schedule=schedule(wait=1, warmup=1, active=5, repeat=1),
                    on_trace_ready=tensorboard_trace_handler('./profiler/' + os.environ['SLURM_JOB_NAME'] 
                                               + '_' + os.environ['SLURM_JOBID'] + '_bs' +
                                               str(mini_batch_size)  + '_is' + str(args.image_size)),
                    profile_memory=True,
                    record_shapes=False, 
                    with_stack=False,
                    with_flops=False
                    )
  • Englober toute la boucle d'apprentissage (validation comprise) dans le context prof.

  • Indiquer au profiler la fin de chaque itération d'apprentissage (avant la validation).

Génération d'une trace profiler¶

Soumission du job. Attention vous sollicitez les noeuds de calcul à ce moment-là.

Remarques :

  • le profilage sera actif sur 5 steps donc nous n'exécutons l'entraînement que sur 7 steps grâce à l'argument --test-nsteps=7.
  • les arguments --num-workers 0 --no-persistent-workers --no-pin-memory --no-non-blocking --prefetch-factor 2 utilisés dans la commande ci-dessous servent à supprimer certaines optimisations déjà présentes dans le script dlojz.py. Ces optimisations seront détaillées dans le prochain chapitre du cours.

Pour soumettre le job, veuillez basculer la cellule suivante du mode Raw NBConvert au mode Code.

In [27]:
command = f'./dlojz2_2.py -b {bs_optim} --image-size {image_size} --test --test-nsteps 7'
command += f' --num-workers 0 --no-persistent-workers --no-pin-memory --no-non-blocking --prefetch-factor 2'
n_gpu = 1
jobid = gpu_jobs_submitter(command, n_gpu, MODULE, name=name,
                    account=account, time_max='00:10:00')
print(f'jobid = {jobid}')
batch job 0: 1 GPUs distributed on 1 nodes with 1 tasks / 1 gpus per node and 8 cpus per task
Submitted batch job 890956
jobid = ['890956']

Puis, rebasculer la cellule précédente en mode Raw NBConvert, afin d'eviter de relancer un job par erreur.

In [28]:
jobid = ['890956']
In [29]:
display_slurm_queue(name)
             JOBID PARTITION     NAME     USER ST       TIME  NODES NODELIST(REASON)
            890956    gpu_p5     toto  ssos938  R       2:39      1 jean-zay-iam16

 Done!

TODO : vérifier qu'une trace a bien été générée dans le répertoire profiler/<name>_<jobid>_bs512_is224/ sous la forme d'un fichier .json:

In [30]:
!tree profiler/
profiler/
└── toto_890956_bs512_is224
    └── jean-zay-iam16_4098532.1709395345752507918.pt.trace.json

1 directory, 1 file

Visualisation des traces profiler avec TensorBoard ¶

TODO : visualiser cette trace grâce à l'application TensorBoard en suivant les étapes suivantes :

  • ouvrir jupyterhub.idris.fr dans un nouvel onglet du navigateur
  • ouvrir une nouvelle instance JupyterHub en cliquant sur Add New JupyterLab Instance
  • sélectionner Spawn server on SLURM node (on va réserver un GPU)
  • sélectionner Tensorboard dans le menu Frontend
  • définir le chemin des logs $WORK/DLO-JZ/profiler dans TensorBoard logs directory
  • sélectionner l'option avancée --partition=Octo-GPU A100 SXM4 with 80 GB GPU mem
  • lancer l'instance TensorBoard

Remarque : le premier démarrage de TensorBoard peut prendre un peu de temps. Il faut parfois faire preuve d'un peu de patience lorsqu'on utilise cet outil mais ça en vaut la peine :)

TODO : en naviguant dans les différents onglets du TensorBoard, chercher à répondre aux questions suivantes :

  • le GPU est-il bien utilisé ? (mémoire max utilisée, occupancy, efficiency)
  • la mémoire CPU est-elle saturée ?
  • les TensorCores sont-ils bien sollicités grâce à l'implémentation de la mixed precision ?
  • quelle partie de l'entraînement est la plus gourmande en temps ? se déroule-t-elle sur le CPU ou le GPU ?
  • essayer de repérer les grandes étapes de calcul sur la timeline de l'exécution (onglet Trace)

IMPORTANT : une fois le TP terminé, penser à quitter l'instance JupyterHub pour libérer le GPU ( > Hub Control Panel > Cancel ).

Garage

TP2_3 : Optimisation du DataLoader¶

Dans ce TP, on utilisera alternativement les scripts dlojz2_1.py (version sans profiler PyTorch) et dlojz2_2.py (version avec profiler PyTorch) qui ne seront pas modifiés.

Contrôle technique (version sous-optimisée)¶

TODO : lancer l'exécution sur 50 itérations (--test-nsteps 50) sans profiling pour passer un contrôle technique qui servira de référence. Cette exécution va prendre quelques minutes (~5min).

Soumission du job. Attention vous sollicitez les noeuds de calcul à ce moment-là.

Pour soumettre le job, veuillez basculer la cellule suivante du mode Raw NBConvert au mode Code.

In [54]:
command = f'./dlojz2_1.py -b {bs_optim} --image-size {image_size} --test --test-nsteps 50'
command += f' --num-workers 0 --no-persistent-workers --no-pin-memory --no-non-blocking --prefetch-factor 2'
n_gpu = 1
jobid = gpu_jobs_submitter(command, n_gpu, MODULE, name=name,
                    account=account, time_max='00:10:00')
print(f'jobid = {jobid}')
batch job 0: 1 GPUs distributed on 1 nodes with 1 tasks / 1 gpus per node and 8 cpus per task
Submitted batch job 891222
jobid = ['891222']
In [58]:
jobid = ['891019']
In [110]:
display_slurm_queue(name)
 Done!
In [59]:
controle_technique(jobid)
Train throughput: 194.93 images/second
GPU throughput: 2298.81 images/second
epoch time: 6574.31 seconds
-----------
training step time average (fwd/bkwd on GPU): 0.222724 sec (8.6%/88.6%) +/- 0.013646
loading step time average (IO + CPU to GPU transfer): 2.403847 sec +/- 0.078146

Découverte de turbo_profiler¶

Pour ce TP, nous avons implémenté un profiler maison léger turbo_profiler basé sur l'outil Chronometer pour visualiser le temps passé sur CPU (DataLoader) et sur GPU (le reste de l'itération). Ce profiler est moins précis mais cela nous permettra de désactiver le profiler PyTorch pour ne pas dégrader les performances et éviter de devoir ouvrir l'outil graphique TensorBoard à chaque fois pour visualiser les informations qui nous intéressent.

TODO : relancer l'exécution précédente sur 16 steps et découvrir le profiler turbo_profiler.

Soumission du job. Attention vous sollicitez les noeuds de calcul à ce moment-là.

Pour soumettre le job, veuillez basculer la cellule suivante du mode Raw NBConvert au mode Code.

In [57]:
command = f'./dlojz2_1.py -b {bs_optim} --image-size {image_size} --test --test-nsteps 16'
command += f' --num-workers 0 --no-persistent-workers --no-pin-memory --no-non-blocking --prefetch-factor 2'
n_gpu = 1
jobid = gpu_jobs_submitter(command, n_gpu, MODULE, name=name,
                    account=account, time_max='00:10:00')
print(f'jobid = {jobid}')
batch job 0: 1 GPUs distributed on 1 nodes with 1 tasks / 1 gpus per node and 8 cpus per task
Submitted batch job 891236
jobid = ['891236']

Puis, rebasculer la cellule précédente en mode Raw NBConvert, afin d'eviter de relancer un job par erreur.

In [62]:
jobid = ['891236']
In [ ]:
display_slurm_queue(name)

TODO : visualiser la sortie de turbo_profiler

In [63]:
# call turbo_profiler
dataloader_trial = turbo_profiler(jobid,dataloader_info=True)
>>> Turbo Profiler >>> Training complete in 95.271499 s

Via le turbo profiler, on va également récupérer et stocker les performances obtenues dans une DataFrame dataloader_trials :

  • initialisation de la DataFrame :
In [64]:
import pandas as pd
dataloader_trials = pd.DataFrame({"jobid":pd.Series([],dtype=str),
                                  "num_workers":pd.Series([],dtype=int),
                                  "persistent_workers":pd.Series([],dtype=str),
                                  "pin_memory":pd.Series([],dtype=str),
                                  "non_blocking":pd.Series([],dtype=str),
                                  "prefetch_factor":pd.Series([],dtype=int),
                                  "drop_last":pd.Series([],dtype=str),
                                  "loading_time":pd.Series([],dtype=float)})
  • stockage du résultat précédent dans la DataFrame :
In [65]:
# store result in "dataloader_trials" DataFrame
dataloader_trials = pd.concat([dataloader_trials,dataloader_trial], ignore_index=True)
  • visualisation du contenu de la DataFrame :
In [66]:
# afficher le tableau récapitulatif, trier par ordre croissant du LOADING_TIME
dataloader_trials.sort_values("loading_time")
Out[66]:
jobid num_workers persistent_workers pin_memory non_blocking prefetch_factor drop_last loading_time
0 891236 0 False False False 2 False 3.229071

Exploration des paramètres d'optimisation du DataLoader¶

L'objectif de ce TP est de réduire le temps passé sur CPU par le DataLoader.

Pour cette étude, on continue à lancer les exécutions sur 16 itérations seulement (--test-nsteps 16) pour avancer plus rapidement.

Les différentes optimisations proposées par le DataLoader de PyTorch sont accessibles dans le script dlojz.py via les arguments :

  • --num-workers <num_workers> (défaut à 10)
  • --persistent-workers (défaut) ou --no-persistent-workers
  • --pin-memory (défaut) ou --no-pin-memory
  • --non-blocking (défaut) ou --no-non-blocking
  • --prefetch-factor <prefetch_factor> (défaut à 3)
  • --drop-last ou --no-drop-last (défaut)

TODO : faire varier ces différents paramètres et observer leurs effets grâce au profiler turbo_profiler. Pour comparer les différents essais, ceux-ci seront stockés dans la DataFrame dataloader_trials initialisée plus tôt.

  1. Modifier un ou plusieurs paramètres du DataLoader et lancer l'exécution :

Soumission du job. Attention vous sollicitez les noeuds de calcul à ce moment-là.

Pour soumettre le job, veuillez basculer la cellule suivante du mode Raw NBConvert au mode Code.

In [50]:
command = f'./dlojz2_1.py -b {bs_optim} --image-size {image_size} --test --test-nsteps 16'

# paramètres d'entrée correspondant aux optimisations du DataLoader
command += ' --num-workers 16' 
command += ' --persistent-workers'
command += ' --pin-memory'
command += ' --non-blocking'
command += ' --prefetch-factor 3'
command += ' --drop-last'

n_gpu = 1
jobid = gpu_jobs_submitter(command, n_gpu, MODULE, name=name,
                    account=account, time_max='00:10:00')
print(f'jobid = {jobid}')
batch job 0: 1 GPUs distributed on 1 nodes with 1 tasks / 1 gpus per node and 8 cpus per task
Submitted batch job 891178
jobid = ['891178']
In [100]:
jobid = ['891058'] # 2 workers
jobid = ['891061'] # 4 workers
jobid = ['891062'] # 8 workers
jobid = ['891063'] # 16 workers
jobid = ['891080'] # + persistent workers
jobid = ['891091'] # + pin memory
jobid = ['891096'] # + non blocking
jobid = ['891108'] # + prefecth factor 3
jobid = ['891178'] # + drop last
In [76]:
display_slurm_queue(name)
             JOBID PARTITION     NAME     USER ST       TIME  NODES NODELIST(REASON)
            891203    gpu_p5     toto  ssos938  R       2:02      1 jean-zay-iam45
            891263    gpu_p5     toto  ssos938  R       1:44      1 jean-zay-iam45
Key interrupt
  1. Visualiser le retour du turbo profiler :
In [101]:
# call turbo_profiler
dataloader_trial = turbo_profiler(jobid,dataloader_info=True)
>>> Turbo Profiler >>> Training complete in 34.062891 s
  1. Stocker le nouveau résultat dans la DataFrame dataloader_trials :
In [102]:
# store result in "dataloader_trials" DataFrame
dataloader_trials = pd.concat([dataloader_trials,dataloader_trial], ignore_index=True)
  1. Visualiser et comparer l'ensemble des résultats :
In [103]:
# afficher le tableau récapitulatif, trier par ordre croissant du LOADING_TIME
dataloader_trials.sort_values("loading_time").drop_duplicates()
Out[103]:
jobid num_workers persistent_workers pin_memory non_blocking prefetch_factor drop_last loading_time
7 891096 16 True True True 2 False 0.000390
6 891091 16 True True False 2 False 0.013792
8 891108 16 True True True 3 False 0.036418
9 891178 16 True True True 3 True 0.057568
3 891062 8 False False False 2 False 0.121440
4 891063 16 False False False 2 False 0.138449
5 891080 16 True False False 2 False 0.141880
2 891061 4 False False False 2 False 0.332657
1 891058 2 False False False 2 False 0.937277
0 891236 0 False False False 2 False 3.229071
  1. Répéter les étapes 1. à 4. jusqu'à avoir trouvé des paramètres d'optimisation satisaisants.

Visualisation des traces profiler avec TensorBoard (version optimisée)¶

TODO : après avoir choisi un lot de paramètres optimal, relancer le job en réactivant le profiler PyTorch (i.e. en utilisant le script dlojz2_2.py) afin de visualiser les traces sous TensorBoard, et les comparer avec la version sous-optimisée étudiée dans le TP2_2.

Soumission du job. Attention vous sollicitez les noeuds de calcul à ce moment-là.

Pour soumettre le job, veuillez basculer la cellule suivante du mode Raw NBConvert au mode Code.

In [51]:
command = f'./dlojz2_2.py -b {bs_optim} --image-size {image_size} --test --test-nsteps 7'

# définir ici les paramètres optimaux 
command += ' --num-workers 16' 
command += ' --persistent-workers'
command += ' --pin-memory'
command += ' --non-blocking'
command += ' --prefetch-factor 2'
command += ' --drop-last'

n_gpu = 1
jobid = gpu_jobs_submitter(command, n_gpu, MODULE, name=name,
                    account=account, time_max='00:10:00')
print(f'jobid = {jobid}')
batch job 0: 1 GPUs distributed on 1 nodes with 1 tasks / 1 gpus per node and 8 cpus per task
Submitted batch job 891195
jobid = ['891195']

Puis, rebasculer la cellule précédente en mode Raw NBConvert, afin d'éviter de relancer un job par erreur.

In [104]:
jobid = ['891195']
In [105]:
display_slurm_queue(name)
 Done!

TODO : vérifier qu'une trace a bien été générée dans le répertoire profiler/<name>_<jobid>_bs512_is224/ sous la forme d'un fichier .json:

In [106]:
!tree profiler/
profiler/
├── toto_890956_bs512_is224
│   └── jean-zay-iam16_4098532.1709395345752507918.pt.trace.json
└── toto_891195_bs512_is224
    └── jean-zay-iam16_4142339.1709396584506111746.pt.trace.json

2 directories, 2 files

TODO : visualiser cette trace grâce à l'application TensorBoard (retrouver la procédure).

IMPORTANT : une fois le TP terminé, penser à quitter l'instance JupyterHub pour libérer le GPU ( > Hub Control Panel > Cancel ).

Contrôle technique (version optimisée)¶

TODO : lancer l'exécution sur 50 itérations (--test-nsteps 50) sans profiling pour passer un nouveau contrôle technique, à comparer avec celui de référence.

Soumission du job. Attention vous sollicitez les noeuds de calcul à ce moment-là.

Pour soumettre le job, veuillez basculer la cellule suivante du mode Raw NBConvert au mode Code.

In [61]:
command = f'./dlojz2_1.py -b {bs_optim} --image-size {image_size} --test --test-nsteps 50'

# définir ici les paramètres optimaux 
command += ' --num-workers 16' 
command += ' --persistent-workers'
command += ' --pin-memory'
command += ' --non-blocking'
command += ' --prefetch-factor 2'
command += ' --drop-last'

n_gpu = 4
jobid = gpu_jobs_submitter(command, n_gpu, MODULE, name=name,
                    account=account, time_max='00:10:00')
print(f'jobid = {jobid}')
batch job 0: 4 GPUs distributed on 1 nodes with 4 tasks / 4 gpus per node and 8 cpus per task
Submitted batch job 891263
jobid = ['891263']
In [107]:
jobid = ['891203']
In [108]:
display_slurm_queue(name)
 Done!
In [109]:
controle_technique(jobid)
Train throughput: 6027.54 images/second
GPU throughput: 6037.52 images/second
epoch time: 212.36 seconds
-----------
training step time average (fwd/bkwd on GPU): 0.339212 sec (48.2%/40.3%) +/- 0.037178
loading step time average (IO + CPU to GPU transfer): 0.000562 sec +/- 0.000464

Garage

In [ ]: